/****************************************************/
// SCRambl - main.cpp
// Interacts with the console
/****************************************************/

#include "stdafx.h"
#include "main.h"
#include "CScrLanguage.h"
#include "CScripts.h"
#include "CDecompiler.h"

bool		g_bExceptionMade	= false;
bool		g_bExit				= false;
bool		g_bForceRunMain		= false;
CScript *	g_pScript			= nullptr;

LARGE_INTEGER g_nBenchmarkTime;

bool OpenScript(const char *szFilePath);
void Command_Pause(const char *params[], char help);
void Command_Info(const char *params[], char help);
void Command_Reset(const char *params[], char help);
void Command_Hash(const char *params[], char help);
void Command_Open(const char *params[], char help);
void Command_List(const char *params[], char help);
void Command_Exit(const char *params[], char help);
void Command_Find(const char *params[], char help);
void Command_Compile(const char *params[], char help);
void Command_Decompile(const char *params[], char help);
void Command_Benchmark(const char *params[], char help);

void BeginBenchmark();
DWORD GetBenchmark();

int main(int argc, char* argv[])
{
	char cmd[512];

	Output("*****************************************");
	Output("*	SCRambl Compiler		*");
	Output("*****************************************");
	Output("*  @ Created by Deji (2012)		*");
	Output("*****************************************");
	
	CConsoleCommand::Add("benchmark", &Command_Benchmark);
	CConsoleCommand::Add("compile", &Command_Compile);
	CConsoleCommand::Add("decompile", &Command_Decompile);
	CConsoleCommand::Add("find", &Command_Find);
	CConsoleCommand::Add("hash", &Command_Hash);
	CConsoleCommand::Add("info", &Command_Info);
	CConsoleCommand::Add("list", &Command_List);
	CConsoleCommand::Add("open", &Command_Open);
	CConsoleCommand::Add("pause", &Command_Pause);
	CConsoleCommand::Add("reset", &Command_Reset);

	g_pProject = new CProject();

	Print("");

	// If we've not passed command line params to compile straight away, use console UI
	if(argc < 2)
	{
mainloop:
		while(true)
		{
			if(ReadStringFromFile(stdin, cmd, sizeof(cmd)))
			{
				if(!g_bExceptionMade)
				{
					trim(cmd);
					if(*cmd)
						CConsoleCommand::ProcessCommand(cmd);
				}
				else
				{
					Alert("SCRambl has encountered an unrecoverable error and must be restarted");
					return FALSE;
				}
			}
			Sleep(100);
		}
	}
	else
	{
		*cmd = '\0';
		
		for(int i=1; i<argc; i++)
		{
			if(*argv[i] == '-')
			{
				if(*cmd)
				{
					CConsoleCommand::ProcessCommand(cmd);
					--i;
					*cmd = '\0';
				}
				else
				{
					if(!argv[i][2])
					{
						switch(argv[i][1])
						{
						case 'c':
							strcat(cmd, "compile");
							break;
						case 'd':
							strcat(cmd, "decompile");
							break;
						case 'i':
							strcat(cmd, "info");
							break;
						case 'o':
							strcat(cmd, "open");
							break;
						case 'p':
							strcat(cmd, "pause");
							break;
						}
					}
					else strcat(cmd, argv[i]);
					strcat(cmd, " ");
				}
			}
			else
			{
				strcat(cmd, "\"");
				strcat(cmd, argv[i]);
				strcat(cmd, "\"");
				strcat(cmd, " ");
			}
		}

		if(*cmd) CConsoleCommand::ProcessCommand(cmd);
		if(g_bForceRunMain) goto mainloop;
	}

	return TRUE;
}

bool OpenScript(const char * szFilePath)
{
	try
	{
		if(g_pScript)
		{
			if(CSubScript * pSubScript = g_pScript->OpenSubScript(szFilePath))
			{
				g_pProject->Add(pSubScript);
				Printf("Added subscript \"%s\" to project files", szFilePath);
				return true;
			}
		}
		else if(g_pScript = new CScript(szFilePath))
		{
			g_pProject->Add(g_pScript);

			if(CScriptFile * pScriptFile = g_pScript->GetFile())
			{
				char szName[MAX_PATH];
				strcpy(szName, pScriptFile->GetPath());
				char * pStr = strrchr(szName, '.');
				if(pStr)
				{
					*pStr++ = '\\';

					strcpy(g_szTempString, pStr);
					*pStr++ = '\0';
					
					if(DoesDirExist(szName))
					{
						WIN32_FIND_DATA fd;
						HANDLE find;
						char * pDirName = strrnthchr(szName, '\\', 1);
						Printf("Detected as MAIN script. Automatically load *.%s files from '%s' as subscripts? (Y/N)", g_szTempString, pDirName ? ++pDirName : szName);
						strcat(szName, "*.");
						strcat(szName, g_szTempString);
						
						switch(char answer = ReadFromFile<char>(stdin))
						{
						case 'Y':
						case 'y':
							g_pProject->SetType(PROJECT_MAIN);

							if((find = FindFirstFile(szName, &fd)) != INVALID_HANDLE_VALUE)
							{
								*strrchr(szName, '*') = '\0';
								do
								{
									if(!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
									{
										if(!(fd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
										{
											char name[MAX_PATH];
											strcpy(name, szName);
											strcat(name, fd.cFileName);

											try
											{
												if(CSubScript * pSubScript = g_pScript->OpenSubScript(name))
												{
													g_pProject->Add(pSubScript);
													Printf("Added subscript \"%s%s\" to project files", pDirName ? pDirName : szName, fd.cFileName);
												}
											}
											catch(const char *)
											{
												Printf("Failed to open subscript \"%s%s\"", pDirName ? pDirName : szName, fd.cFileName);
											}
										}
									}
								}
								while(FindNextFile(find, &fd));
								FindClose(find);
							}
							break;
						default:
							break;
						}
						
						ReadStringFromFile(stdin, g_szTempString, sizeof(g_szTempString));
					}
				}
				Printf("Added script \"%s\" to project files", pScriptFile->GetName());
			}
			
			return true;
		}
	}
	catch(const char *e)
	{
		Error(e);
	}
	return false;
}
bool Compile()
{
	try
	{
		if(!g_pProject) throw("No project!");
		
		switch(g_pProject->GetType())
		{
		case PROJECT_MAIN:
			Print("Building MAIN project...");
			break;
		case PROJECT_CLEO:
			Print("Building CLEO project...");
			break;
		case PROJECT_SCRIPT:
			Print("Building SCRIPT project...");
			break;
		case PROJECT_MOD:
			Print("Building MOD project...");
			break;
		default:
			Print("Building project...");
			break;
		}
		
		BeginBenchmark();

		if(g_pProject->Build())
		{
			Printf("Built project in %dms", GetBenchmark());
			return true;
		}
	}
	catch(const char *e){Error(e);}
	return false;
}
bool Decompile(const char * szInputPath, const char * szOutputPath)
{
	try
	{
		if(FILE * hInFile = OpenFile(szInputPath, "rb"))
		{
			if(FILE * hOutFile = OpenFile(szOutputPath, "wt"))
			{
				BeginBenchmark();

				/*if(int bytes = CDecompiler::Decompile(hInFile, hOutFile))
				{
					Printf("Decompiled %d bytes to \"%s\" in %dms", bytes, szOutputPath, GetBenchmark());
					CloseFile(hInFile);
					CloseFile(hOutFile);
					return true;
				}
				else throw("Decompilation failed");*/

				CloseFile(hOutFile);
			}
			else throw("Failed to open output file");

			CloseFile(hInFile);
		}
		else throw("Failed to open input file");
	}
	catch(const char *e){Error(e);}
	return false;
}

void BeginBenchmark()
{
	QueryPerformanceCounter(&g_nBenchmarkTime);
}
DWORD GetBenchmark()
{
    LARGE_INTEGER diff;
    LARGE_INTEGER freq;
    QueryPerformanceCounter(&diff);
    diff.QuadPart -= g_nBenchmarkTime.QuadPart;
    diff.QuadPart *= 1000;
    QueryPerformanceFrequency(&freq);
    return (DWORD)(diff.QuadPart / freq.QuadPart);
}

void Command_Pause(const char * params[], char help)
{
	if(help)
	{
		Print("Run in console mode after execution of command line params");
		return;
	}
	g_bForceRunMain = true;
}
void Command_Info(const char * params[], char help)
{
	if(help)
	{
		Print("Display information about the current compiler state including open script info");
		return;
	}
	
	if(g_pScript)
	{
		Print(" * SCRIPT OPEN * ");
		Printf("Name: %s", g_pScript->GetFile()->GetName());
		Printf("File: %s", g_pScript->GetDirectoryPath());
	}
}
void Command_Reset(const char * params[], char help)
{
	if(help)
	{
		Print("Clears script identifier lists and parsed data");
		return;
	}

	Parser.Reset();
}
void Command_Hash(const char * params[], char help)
{
	if(params[0])
	{
		DWORD hash = GenerateHash(params[0]);
		Printf("%X", hash);
	}
}
void Command_Open(const char * params[], char help)
{
	if(help)
	{
		Print("USAGE\topen [file path]");
		return;
	}

	if(params[0])
	{
		OpenScript(params[0]);
		return;
	}

	Error("No valid file path specified");
}
void Command_Compile(const char * params[], char help)
{
	if(help)
	{
		Print("USAGE\tcompile");
		return;
	}

	Compile();
}
void Command_Decompile(const char * params[], char help)
{
	if(help)
	{
		Print("USAGE\tdecompile [in path] [out path]");
		return;
	}

	if(params[0] && params[1])
	{
		BeginBenchmark();
		Decompile(params[0], params[1]);
		GetBenchmark();
		return;
	}

	if(params[0]) Error("No valid input file specified");
	else Error("No valid output destination specified");
}
void Command_Benchmark(const char * params[], char help)
{
	static const char list_types[][16] = {
		"commands"
	};

	int i;

	if(params[0])
	{
		for(i=0; i<sizeof(list_types); i++)
		{
			if(!stricmp(params[0], list_types[i]))
			{
				i++;
				break;
			}
		}
	}

	switch(i)
	{
	case 1:
#ifdef USE_BINARY_SEARCHING
		Print("Command Search Benchmark (binary tree mode)");
#endif
		Print("Matching all commands in pool.");
		BeginBenchmark();
		
		char search[256];
		int n = CCommands::Count();
		for(int reps=0; reps < 1; reps++)
		{
			for(i=0; i<n; i++)
			{
				CCommand *pCommand = CCommands::GetByIndex(i);
				pCommand->GetName(search);
				pCommand = CCommands::GetByName(strupr(search));
			}
		}

		Printf("Searched %d commands in %dms", n, GetBenchmark());
		break;
	}
}
void Command_List(const char * params[], char help)
{
	static const char list_types[][16] = {
		"commands",
		"operators",
	};
	
	int i = 0;

	if(help)
	{
		Print("USAGE\tlist [type]");
		Print("  List types:");
		for(i=0; i<sizeof(list_types)/16; i++)
		{
			Printf("    %s", list_types[i]);
		}
		return;
	}

	if(params[0])
	{
		for(i=0; i<sizeof(list_types); i++)
		{
			if(!stricmp(params[0], list_types[i]))
			{
				i++;
				break;
			}
		}
	}

	int n;
	
	switch(i)
	{
	case 1:
		n = CCommands::Count();
		for(i=0; i<n; i++)
		{
			CCommand *pCommand = CCommands::GetByIndex(i);
			Printf("%04X \t%s", pCommand->GetId(), pCommand->GetName());
		}
		break;
	case 2:
		n = COperators::Count();
		for(i=0; i<n; i++)
		{
			auto pOperator = COperators::GetByIndex(i);
			auto pOps = pOperator->GetOperations();
			int j = 0;
			for(auto pLink = pOps->Begin(); pLink != pOps->Bad(); ++pLink, ++j)
			{
				auto		*	LType = pLink->GetLeftArg()->GetType(),
							*	RType = pLink->GetRightArg()->GetType();
				
				char op[MAX_OPERATOR_CHARS+1] = "";
				pOperator->GetOperatorString(op);
				Printf("%04X \t%s %s %s", pLink->GetId(), LType->GetName(), op, RType->GetName());
			}
		}
		break;
	case 0:
		Print("USAGE: list [type]");
		break;
	default:
		Print("ERROR: Unknown list type");
		break;
	};
}
void Command_Exit(const char *params[], char help)
{
	if(help)
	{
		Print("Exits SCRambl console UI");
		return;
	}

	g_bExit = true;
}
void Command_Find(const char * params[], char help)
{
	static const char list_types[][16] = {
		"command",
	};
	
	int i = 0;

	if(help)
	{
		Print("USAGE\tfind [type] [name]");
		Print("  Find types:");
		for(i=0; i<sizeof(list_types)/16; i++)
		{
			Printf("    %s", list_types[i]);
		}
		return;
	}

	if(params[0])
	{
		for(i=0; i<sizeof(list_types); i++)
		{
			if(!stricmp(params[0], list_types[i]))
			{
				i++;
				break;
			}
		}
	}

	switch(i)
	{
	case 1:
		char search[256];
		if(params[1])
		{
			szncpy(search, params[1], 256);
			if(CCommand *pCommand = CCommands::GetByName(strupr(search)))
				Printf("%04X \t%s", pCommand->GetId(), pCommand->GetName());
			else
				Print("Not found");
		}
		else goto print_usage;
		break;
	case 0:
print_usage:
		Print("USAGE\tfind [type] [name]");
		break;
	default:
		Print("ERROR: Unknown list type");
		break;
	};
}